package scalaz

object TestApp extends App {

  def sum(nums: List[Int]) = nums.reduce(_ + _)

  def sumDouble(nums: List[Double]) = nums.reduce(_ + _)

  def sumNumeric[A](nums: List[A])(implicit A: Numeric[A]): A = nums.reduce(A.plus) // <: Numeric but String?

  def sumString(strs: List[String]) = strs.reduce(_ + _)

  //Making Generic

  trait Addable[A] {
    def plus(x: A, y: A): A
  }

  object Addable {
    //    numericIsAddable
    implicit def numericIsAddable[A](implicit A: Numeric[A]): Addable[A] = new Addable[A] {
      override def plus(x: A, y: A) = A.plus(x, y)
    }
    //    stringIsAddable
    implicit val stringIsAddable: Addable[String] = new Addable[String] {
      override def plus(x: String, y: String): String = x + y
    }
  }

  def sumGeneric[A](list: List[A])(implicit A: Addable[A]): A = list.reduce(A.plus)

  assert(sumGeneric(List("a", "b")) == "ab")

  trait Monoid[A] {
    def mappend(m1: A, m2: A): A
    def mzero: A
  }

  object Monoid {
    implicit def numericMonoid[A](implicit A: Numeric[A]): Monoid[A] = new Monoid[A] {
      override def mappend(x: A, y: A): A = A.plus(x, y)
      override def mzero: A = A.zero
    }

    implicit val stringMonoid: Monoid[String] = new Monoid[String] {
      override def mappend(x: String, y: String): String = x + y
      override def mzero = ""
    }
  }

  def sumMonoid[A](xs: List[A])(implicit A: Monoid[A]): A = xs.foldLeft(A.mzero)(A.mappend)

  assert(sumMonoid(List("a", "b", "c")) == "abc")

  /* def multiMonoid[A](implicit A: Numeric[A]): Monoid[A] = new Monoid[A] {
    override def mappend(x: A, y: A): A = A.(A.toInt(x) * A.toInt(y))
    override def mzero = A.zero
  }*/

  val multiMonoid: Monoid[Int] = new Monoid[Int] {
    override def mappend(x: Int, y: Int): Int = x * y
    override def mzero = 1
  }

  println(sumMonoid(List(1, 2, 3, 4))(multiMonoid))

  //FoldLeft
  object FoldLeftList {
    def foldLeft[A, B](xs: List[A], b: B, f: (B, A) => B) = xs.foldLeft(b)(f)
  }

  def sum[A: Monoid](xs: List[A]): A = { // or def sum[A](xs:List[A])(implicit A:Monoid[A]):A ={}
    val m = implicitly[Monoid[A]]
    FoldLeftList.foldLeft(xs, m.mzero, m.mappend)
  }

  trait FoldLeft[F[_]] {
    def foldLeft[A, B](xs: F[A], b: B, f: (B, A) => B): B
  }
  object FoldLeft {
    implicit val FoldLeftList: FoldLeft[List] = new FoldLeft[List] {
      def foldLeft[A, B](xs: List[A], b: B, f: (B, A) => B) = xs.foldLeft(b)(f)
    }
  }

  def sum[M[_]: FoldLeft, A: Monoid](xs: M[A]): A = {
    val m = implicitly[Monoid[A]]
    val fl = implicitly[FoldLeft[M]]
    fl.foldLeft(xs, m.mzero, m.mappend)
  }

  def sum2[M[_]:FoldLeft, A](xs: M[A])(implicit A: Monoid[A]):A = {
     val fl = implicitly[FoldLeft[M]]
    fl.foldLeft(xs, A.mzero, A.mappend)
  }

}